package cn.xeblog.plugin.game.landlords;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.xeblog.commons.entity.User;
import cn.xeblog.commons.entity.game.GameRoom;
import cn.xeblog.commons.entity.game.landlords.AllocPokerDTO;
import cn.xeblog.commons.entity.game.landlords.LandlordsGameDTO;
import cn.xeblog.commons.entity.game.landlords.Poker;
import cn.xeblog.commons.entity.game.landlords.PokerInfo;
import cn.xeblog.commons.entity.game.landlords.PokerModel;
import cn.xeblog.commons.enums.Game;
import cn.xeblog.plugin.action.GameAction;
import cn.xeblog.plugin.annotation.DoGame;
import cn.xeblog.plugin.cache.DataCache;
import cn.xeblog.plugin.game.AbstractGame;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.components.JBScrollPane;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * 斗地主游戏
 *
 * @author anlingyi
 * @date 2022/6/2 1:13 下午
 */
@DoGame(Game.LANDLORDS)
public class LandlordsGame extends AbstractGame<LandlordsGameDTO> {

  /**
   * 当前的debug模式
   */
  public static DebugMode debugMode;
  /**
   * 当前的窗口模式
   */
  public static WindowMode windowMode;
  /**
   * 重新开始游戏计数，游戏每开始一次计数都会+1，用于解决多线程操作中判断当前游戏是不是同一局游戏。线程操作之前会复制一份当前的计数副本，当线程执行时，先判断当前的计数是否等于线程副本的计数，计数不一致就表示不是同一局游戏，就结束线程。
   */
  private AtomicInteger restartCounter;
  /**
   * 开始面板
   */
  private JPanel startPanel;
  /**
   * 游戏结束按钮
   */
  private JButton gameOverButton;
  /**
   * 出牌按钮
   */
  private JButton outPokerButton;
  /**
   * 重置按钮
   */
  private JButton resetButton;
  /**
   * 提示按钮
   */
  private JButton helpButton;
  /**
   * 不出牌（过）按钮
   */
  private JButton notOutPokerButton;
  /**
   * 不叫分按钮
   */
  private JButton notCallScoreButton;
  /**
   * 叫分按钮
   */
  private JButton callScoreButton;
  /**
   * 面板中心提示文本组件
   */
  private JLabel tipsLabel;
  /**
   * 出牌面板
   */
  private JPanel outPokerPanel;
  /**
   * 当前玩家所在主面板的顶部面板，即玩家重置、出牌、提示按钮面板
   */
  private JPanel playerTopPanel;
  /**
   * 主面板标题文本组件
   */
  private JLabel titleLabel;
  /**
   * 返回游戏按钮
   */
  private JButton backButton;
  /**
   * 底部手牌列表显示面板，即当前玩家手牌列表面板，
   */
  private JPanel pokerListPanel;
  /**
   * 出牌显示面板
   */
  private JPanel showPokerPanel;
  /**
   * 玩家主面板
   */
  private JPanel playerMainPanel;
  /**
   * 手牌主面板
   */
  private JPanel pokerMainPanel;

  /**
   * 玩家缓存[玩家名称，斗地址玩家信息]
   */
  private Map<String, Player> playerMap;
  /**
   * AI玩家列表(玩家名称)
   */
  private static List<String> aiPlayerList;

  /**
   * AI玩家行为Map[玩家名称，玩家相关行为]
   */
  private Map<String, PlayerAction> aiPlayerActionMap;
  /**
   * 房间内玩家名称
   */
  private List<String> userList;
  /**
   * 玩家行为帮助工具
   */
  private PlayerAction helpPlayerAction;

  static {
    aiPlayerList = new ArrayList<>();
    aiPlayerList.add("AI·傻瓜蛋");
    aiPlayerList.add("AI·小傻子");
    aiPlayerList.add("AI·小笨蛋");
  }

  /**
   * 状态 0.初始化 1.摸牌 2.叫分 3.出牌 4.失败 5.胜利
   */
  private int state;

  /**
   * 当前玩家
   */
  private PlayerNode currentPlayer;

  /**
   * 玩家手牌
   */
  private List<Poker> pokers;

  /**
   * 底牌
   */
  private List<Poker> lastPokers;

  /**
   * 已选中的牌
   */
  private List<Poker> selectedPokers;

  /**
   * 当前出牌信息
   */
  private PokerInfo pokerInfo;

  /**
   * 上家出牌信息
   */
  private PokerInfo lastPokerInfo;

  /**
   * 上家出牌玩家
   */
  private String lastPlayer;

  /**
   * 接收计数器
   */
  private AtomicInteger receiveCounter;

  /**
   * 叫分最大的玩家
   */
  private Player maxScorePlayer;

  /**
   * 最大叫分
   */
  private int maxScore;

  /**
   * 当前游戏模式
   */
  private GameMode gameMode;

  /**
   * 游戏模式
   */
  @AllArgsConstructor
  @Getter
  private enum GameMode {
    /**
     * 经典模式
     */
    CLASSIC("经典模式"),
    /**
     * 不洗牌模式
     */
    NOT_SHUFFLED("不洗牌模式");
    /**
     * 游戏模式名称
     */
    private String name;

    /**
     * 通过名称获取游戏模式
     *
     * @param name 游戏模式名称
     * @return 游戏模式
     */
    public static GameMode getMode(String name) {
      return Arrays.stream(values()).filter(model -> model.name.equals(name)).findFirst().orElse(GameMode.CLASSIC);
    }
  }

  /**
   * 斗地主玩家信息
   */
  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  private class Player {

    /**
     * 玩家节点
     */
    private PlayerNode playerNode;
    /**
     * 当前玩家所在的面板
     */
    private JPanel panel;
    /**
     * 前玩家所在的面板提示文本组件
     */
    private JLabel tipsLabel;
    /**
     * 角色文本组件
     */
    private JLabel roleLabel;
    /**
     * 昵称文本组件
     */
    private JLabel nicknameLabel;

    /**
     * 构建玩家
     *
     * @param playerNode 玩家节点
     * @param panel      当前玩家所在的面板
     */
    public Player(PlayerNode playerNode, JPanel panel) {
      this.playerNode = playerNode;
      this.panel = panel;
    }

    /**
     * 显示提示
     *
     * @param tips 提示文本
     */
    public void showTips(String tips) {
      tipsLabel.setText(tips);
      tipsLabel.updateUI();
    }

    /**
     * 刷新角色文本组件
     */
    public void flushRole() {
      roleLabel.setText(playerNode.getRoleInfo() + "：" + playerNode.getPokerTotal());
      roleLabel.updateUI();
    }

    /**
     * 刷新昵称文本组件
     */
    public void flushNickname() {
      // 如果为hard模式，显示别名，默认显示玩家名
      if (isHard()) {
        nicknameLabel.setText(playerNode.getAlias());
      } else {
        nicknameLabel.setText(playerNode.getPlayer());
      }
      nicknameLabel.updateUI();
    }
  }

  /**
   * 初始化属性
   */
  private void initValue() {
    state = 0;
    maxScore = 0;
    pokers = null;
    lastPokers = null;
    pokerInfo = null;
    lastPokerInfo = null;
    lastPlayer = null;
    currentPlayer = null;
    maxScorePlayer = null;
    playerMap = null;
    selectedPokers = new ArrayList<>();
    receiveCounter = new AtomicInteger();
    userList = new ArrayList<>();
    aiPlayerActionMap = new HashMap<>();
  }

  @Override
  protected void init() {
    // 初始化状态，标记为初始化（0.初始化 1.摸牌 2.叫分 3.出牌 4.失败 5.胜利）
    state = 0;
    // 重新开始游戏计数+1
    if (restartCounter == null) {
      restartCounter = new AtomicInteger();
    }
    restartCounter.incrementAndGet();

    mainPanel.removeAll();

    // 主面板启用、可见且设置面板最小大小
    mainPanel.setLayout(null);
    mainPanel.setEnabled(true);
    mainPanel.setVisible(true);
    mainPanel.setMinimumSize(new Dimension(150, 260));

    // 开始面板
    startPanel = new JPanel();
    startPanel.setBounds(10, 10, 120, 260);
    mainPanel.add(startPanel);

    // 开始面板添加斗地主文本标题
    JLabel title = new JLabel("斗地主！");
    title.setFont(new Font("", 1, 14));
    startPanel.add(title);

    // 垂直盒子
    Box vBox = Box.createVerticalBox();
    startPanel.add(vBox);
    vBox.add(Box.createVerticalStrut(20));

    // 将游戏模式文本加入盒子
    JLabel modelLabel = new JLabel("游戏模式：");
    modelLabel.setFont(new Font("", 1, 13));
    vBox.add(modelLabel);

    vBox.add(Box.createVerticalStrut(5));

    // 游戏模式下拉列表框
    ComboBox gameModeBox = new ComboBox();
    // 设置此组件的首选大小
    gameModeBox.setPreferredSize(new Dimension(40, 30));
    for (GameMode value : GameMode.values()) {
      gameModeBox.addItem(value.getName());
    }
    // 默认选中经典模式
    gameMode = GameMode.CLASSIC;
    gameModeBox.setSelectedItem(gameMode.getName());
    // 游戏模式监听事件
    gameModeBox.addActionListener(l -> {
      GameMode selectedGameMode = GameMode.getMode(gameModeBox.getSelectedItem().toString());
      if (selectedGameMode != null) {
        gameMode = selectedGameMode;
      }
    });
    vBox.add(gameModeBox);

    vBox.add(Box.createVerticalStrut(20));

    // 开始游戏按钮
    vBox.add(getStartGameButton());

    // 当用户在线时，显示“创建房间“按钮
    if (DataCache.isOnline) {
      // 房间人数
      List<Integer> numsList = new ArrayList();
      numsList.add(2);
      numsList.add(3);

      // 游戏模式
      List<String> gameModeList = new ArrayList<>();
      for (GameMode mode : GameMode.values()) {
        gameModeList.add(mode.getName());
      }

      // 添加创建房间按钮
      vBox.add(getCreateRoomButton(numsList, gameModeList));
    }

    // 退出游戏按钮
    vBox.add(getExitButton());
    // 刷新界面
    mainPanel.updateUI();
  }

  @Override
  protected void start() {
    // 初始化默认值
    initValue();
    // 游戏房间
    GameRoom gameRoom = getRoom();
    // 房间存在时，添加房间内的玩家
    if (gameRoom != null) {
      gameMode = GameMode.getMode(gameRoom.getGameMode());
      userList.addAll(gameRoom.getUsers().keySet());
    } else {
      // 添加当前用户名称
      userList.add(GameAction.getNickname());
    }

    // 构建游戏节点
    buildPlayerNode();
    // 显示游戏面板
    showGamePanel();
    // 设置当前状态为摸牌
    state = 1;
    showTips("请等待...");

    // 展示提示
    if (userList.size() < 3) {
      showTips("正在加入机器人...");
    } else {
      showTips("等待发牌...");
    }

    // 非联机，加入AI
    if (gameRoom == null) {
      allPlayersGameStarted();
    }
  }

  @Override
  protected void allPlayersGameStarted() {
    // 当所有玩家游戏开始，且当前用户是房主
    if (isHomeowner()) {
      // 当前用户总数
      int usersTotal = userList.size();
      // 房间差人数
      int nums = 3 - usersTotal;
      invoke(() -> {
        // 少人则补ai玩家
        if (nums > 0) {

          // AI玩家列表
          List<String> joinedAIList = new ArrayList<>(aiPlayerList);
          // AI玩家列表移除用户列表玩家
          joinedAIList.removeAll(userList);
          // 随机排序一次
          Collections.shuffle(joinedAIList);
          // 加入的AI玩家
          List<String> aiList = joinedAIList.subList(0, nums);
          // 添加ai玩家
          aiList.forEach(ai -> aiPlayerActionMap.put(ai, null));
          // 发送加入AI玩家信息
          sendMsg(LandlordsGameDTO.MsgType.JOIN_ROBOTS, GameAction.getNickname(), new ArrayList<>(aiList));
        } else {
          // 不差人
          allocPokersMsg();
        }
      }, 500);
    }
  }

  /**
   * 分牌消息
   */
  private void allocPokersMsg() {
    // 随机优先
    int priority = new Random().nextInt(3);
    // 按游戏模式分牌
    List<List<Poker>> allocPokers = PokerUtil.allocPokers(gameMode == GameMode.CLASSIC);
    // 获取玩家列表
    List<String> playerList = userList;
    // 给玩家发送分牌消息
    for (int i = 0; i < playerList.size(); i++) {
      sendMsg(LandlordsGameDTO.MsgType.ALLOC_POKER, playerList.get(i),
          new AllocPokerDTO(allocPokers.get(i), allocPokers.get(3), priority == i));
    }
  }

  /**
   * 发送消息
   *
   * @param msgType 消息类型
   * @param data    数据
   */
  private void sendMsg(LandlordsGameDTO.MsgType msgType, Object data) {
    sendMsg(msgType, currentPlayer.getPlayer(), data);
  }

  /**
   * 发送消息
   *
   * @param msgType 消息类型
   * @param player  玩家
   * @param data    数据
   */
  private void sendMsg(LandlordsGameDTO.MsgType msgType, String player, Object data) {
    // 初始化状态，跳过
    if (state == 0) {
      return;
    }

    // 拼装消息，联机则发送游戏消息
    LandlordsGameDTO dto = new LandlordsGameDTO();
    dto.setMsgType(msgType);
    dto.setPlayer(player);
    dto.setData(data);
    if (getRoom() != null) {
      sendMsg(dto);
    }
    invoke(() -> handle(dto));
  }

  /**
   * 控制机器人叫分
   *
   * @param playerNode 玩家节点
   * @param score      分数
   */
  private void controlRobotCallScore(PlayerNode playerNode, int score) {
    invokeRobot(() -> sendMsg(LandlordsGameDTO.MsgType.CALL_SCORE, playerNode.getPlayer(),
        aiPlayerActionMap.get(playerNode.getPlayer()).callScore(score)));
  }

  /**
   * 控制机器人出牌
   *
   * @param playerNode   玩家节点
   * @param outPlayer    出牌节点
   * @param outPokerInfo 出牌信息
   */
  private void controlRobotOutPoker(PlayerNode playerNode, PlayerNode outPlayer, PokerInfo outPokerInfo) {
    invokeRobot(() -> sendMsg(LandlordsGameDTO.MsgType.OUT_POKER, playerNode.getPlayer(),
        aiPlayerActionMap.get(playerNode.getPlayer()).outPoker(outPlayer, outPokerInfo)));
  }

  /**
   * 调用机器人
   *
   * @param runnable 任务
   */
  private void invokeRobot(Runnable runnable) {
    // 重新开始游戏计数
    int restart = restartCounter.get();
    invoke(() -> {
      // 不是同一局游戏,跳过
      if (restart != restartCounter.get()) {
        return;
      }
      // 运行任务
      runnable.run();
    }, 1800);
  }

  @Override
  public void handle(LandlordsGameDTO body) {
    // 当状态为初始化时，跳过
    if (state == 0) {
      return;
    }

    // 当前玩家信息
    Player player = playerMap.get(body.getPlayer());
    // 玩家节点
    PlayerNode playerNode = player.getPlayerNode();
    // 下一个玩家节点
    PlayerNode nextPlayerNode = playerNode.getNextPlayer();
    // 下一个玩家
    Player nextPlayer = null;
    // 下一玩家的AI行为类
    PlayerAction aiPlayerAction = null;

    // 初始化下一玩家及其行为类
    if (nextPlayerNode != null) {
      nextPlayer = playerMap.get(nextPlayerNode.getPlayer());
      aiPlayerAction = aiPlayerActionMap.get(nextPlayerNode.getPlayer());
    }

    // 当前是否房主
    boolean isHomeowner = isHomeowner();
    // 当前玩家是不是自己
    boolean isMe = playerNode == currentPlayer;
    // 是否可以控制机器人（必须是房主且存在AI的实例对象时才能控制机器人）
    boolean controlRobot = isHomeowner && aiPlayerAction != null;
    boolean isHard = isHard();

    switch (body.getMsgType()) {
      case JOIN_ROBOTS:
        // 加入机器人
        List<String> robotList = (List<String>) body.getData();
        // 添加机器人到用户列表
        userList.addAll(robotList);
        // 构建游戏玩家节点，将所有玩家节点形成一个闭环
        buildPlayerNode();
        // 显示游戏面板
        showGamePanel();
        // 提示
        showTips("等待发牌...");
        // 如果是房主，发送发牌提示
        if (isHomeowner) {
          invoke(() -> allocPokersMsg(), 500);
        }
        break;
      case ALLOC_POKER:  // 分牌
        // 当所有玩家接受并且都准备好了,标记状态为叫分阶段
        if (receivedAndOk()) {
          state = 2;
        }

        // 分牌信息
        AllocPokerDTO allocPokerDTO = (AllocPokerDTO) body.getData();
        // 分配的手牌
        List<Poker> allocPokerList = allocPokerDTO.getPokers();
        // 设置玩家节点手牌数量
        playerNode.setPokerTotal(allocPokerList.size());
        // 当前玩家是自己
        if (isMe) {
          // 设置不能控制机器人
          controlRobot = false;
          // 设置手牌
          pokers = allocPokerList;
          // 设置底牌
          lastPokers = allocPokerDTO.getLastPokers();
          // 帮助行为类设置扑克组
          helpPlayerAction.setPokers(new ArrayList<>(pokers));
          // 提示
          showTips(isHard ? "Confirming Master Machine..." : "正在确定地主...");
        } else {
          // 获取AI行为类
          aiPlayerAction = aiPlayerActionMap.get(playerNode.getPlayer());
          // 是房主且存在AI的实例对象，设置为可以控制机器人
          controlRobot = aiPlayerAction != null && isHomeowner;
        }

        // 如果可以控制机器人，设置手牌
        if (controlRobot) {
          aiPlayerAction.setPokers(allocPokerList);
        }
        // 刷新玩家面板
        flushPlayerPanel(player);

        // 优先叫分
        if (allocPokerDTO.isPrioritized()) {
          if (isMe) { // 如果是自己优先时
            spinMoment(100);
            // 展示叫分按钮
            showCallScoreButton(0);
          } else {
            // 展示提示
            player.showTips(isHard ? "Priority!" : "优先叫地主！");
            // 如果可以控制机器人，则控制机器人叫分
            if (controlRobot) {
              controlRobotCallScore(playerNode, 0);
            }
          }
        }
        break;
      case CALL_SCORE:
        // 叫分
        String tips;
        // 分数
        int score = (int) body.getData();
        // 不叫
        if (score == 0) {
          tips = isHard ? "Abandon!" : "不叫！";
        } else {
          // 叫分
          tips = score + (isHard ? " Point!" : "分！");
          // 设置最大的分数、设置叫分最大的玩家
          if (score > maxScore) {
            maxScore = score;
            maxScorePlayer = player;
          }
        }

        // 展示提示
        if (isMe) {
          showPlayerTips(tips);
        } else {
          player.showTips(tips);
        }

        // 是否已经准备好了，即确定地主了
        boolean isOk = receivedAndOk();
        // 都准备好了且未确定地主
        if (isOk && maxScorePlayer == null) {
          // 进度摸牌阶段
          state = 1;
          // 展示提示
          showTips(isHard ? "Master Machine Not Confirmed! Once Again..." : "未确定地主！正在重新发牌...");
          // 当前为房主时
          if (isHomeowner) {
            // 判断当前游戏是不是同一局游戏
            int restartValue = restartCounter.get();
            invoke(() -> {
              // 不是同一局则跳过
              if (restartCounter.get() != restartValue) {
                return;
              }

              // 发送分牌消息
              allocPokersMsg();
            }, 2000);
          }
          break;
        }

        // 没有都准备好且当前分数为3 或者已经都准备且最大分数的玩家不为空，此时已经确定地主了，到了出牌阶段
        if (!isOk && score == 3 || isOk && maxScorePlayer != null) {
          state = 3;

          // 获取叫分最大的玩家
          PlayerNode maxScorePlayerNode = maxScorePlayer.getPlayerNode();
          // 设置为地主
          maxScorePlayerNode.setRole(2);
          // 手牌数+3
          maxScorePlayerNode.setPokerTotal(playerNode.getPokerTotal() + 3);
          // 当前是否为地主
          boolean masterIsMe = maxScorePlayerNode == currentPlayer;
          // 展示提示
          if (isHard) {
            showTips(maxScorePlayerNode.getAlias() + (masterIsMe ? " (me)" : "") + " is the master！");
          } else {
            showTips(maxScorePlayerNode.getPlayer() + (masterIsMe ? "(你)" : "") + "成为地主！");
          }
          // 显示地主牌
          showPokers(lastPokers);

          // 设置农民角色
          PlayerNode maxScorePlayerPrevPlayerNode = maxScorePlayerNode.getPrevPlayer();
          PlayerNode maxScorePlayerNextPlayerNode = maxScorePlayerNode.getNextPlayer();
          maxScorePlayerPrevPlayerNode.setRole(1);
          maxScorePlayerNextPlayerNode.setRole(1);

          // 刷新角色UI
          Player masterPrevPlayer = playerMap.get(maxScorePlayerPrevPlayerNode.getPlayer());
          Player masterNextPlayer = playerMap.get(maxScorePlayerNextPlayerNode.getPlayer());
          masterPrevPlayer.flushRole();
          masterNextPlayer.flushRole();

          // 清空提示
          if (maxScorePlayerPrevPlayerNode != currentPlayer) {
            masterPrevPlayer.showTips("");
          }
          if (maxScorePlayerNextPlayerNode != currentPlayer) {
            masterNextPlayer.showTips("");
          }

          Player maxScorePlayer = playerMap.get(maxScorePlayerNode.getPlayer());
          // 当前玩家是地主时
          if (maxScorePlayerNode == currentPlayer) {
            // 底牌加入手牌中
            pokers.addAll(lastPokers);
            // 玩家实例对象设置底牌
            helpPlayerAction.setLastPokers(new ArrayList<>(lastPokers));
            // 手牌排序
            PokerUtil.sorted(pokers, true);
            // 刷新玩家面板
            flushPlayerPanel(maxScorePlayer);
            spinMoment(100);
            // 显示出牌按钮
            showOutPokerButton(true);
          } else { // AI是地主时
            // 展示提示
            this.maxScorePlayer.showTips(isHard ? "Debugging..." : "思考中...");
            // 刷新角色
            maxScorePlayer.flushRole();
            // 玩家实例对象
            aiPlayerAction = aiPlayerActionMap.get(maxScorePlayerNode.getPlayer());
            // 当前可以控制AI时，设置底牌且控制机器人出牌
            if (isHomeowner && aiPlayerAction != null) {
              aiPlayerAction.setLastPokers(lastPokers);
              controlRobotOutPoker(maxScorePlayerNode, null, null);
            }
          }
        } else {
          // 当下一个玩家是当前玩家时，展示叫分按钮
          if (nextPlayer.getPlayerNode() == currentPlayer) {
            showCallScoreButton(maxScore);
          } else {
            // 当下一个玩家不是当前玩家时，展示提示
            nextPlayer.showTips(isHard ? "Confirm..." : "叫分中...");
            // 如果可以控制机器人
            if (controlRobot) {
              // 控制机器人叫分
              controlRobotCallScore(nextPlayerNode, maxScore);
            }
          }
        }
        break;
      case OUT_POKER:
        // 出牌信息
        PokerInfo outPokerInfo = (PokerInfo) body.getData();
        // 不存在出牌信息，即过
        if (outPokerInfo == null) {
          String tips2 = isHard ? "Run!" : "过！";
          if (isMe) {
            showPlayerTips(tips2);
          } else {
            player.showTips(tips2);
          }
        } else {
          // 如果是单张顺子，排序
          if (outPokerInfo.getPokerModel() == PokerModel.SHUN_ZI_SINGLE) {
            PokerUtil.sorted(outPokerInfo.getPokers(), false);
          }

          // 扑克显示的牌型
          String pokerModeInfo = getPokerDisplayModel(outPokerInfo);
          // 展示提示
          if (isMe) {
            showPlayerTips(pokerModeInfo);
          } else {
            player.showTips(pokerModeInfo);
          }

          // 上一次的角色
          String lastPlayerRole = playerNode.getRoleInfo();
          // 设置上家出牌玩家
          lastPlayer = playerNode.getPlayer();
          // 设置上家出牌信息
          lastPokerInfo = outPokerInfo;
          // 游戏是否结束
          boolean isOver = playerNode.minusPoker(outPokerInfo.getPokers().size()) == 0;
          // 展示提示
          if (isHard) {
            String hardTips = "[" + lastPlayerRole + "] " + playerNode.getAlias() + (isMe ? " (me)" : "") + " debug result";
            showTips(hardTips);
          } else {
            showTips("[" + lastPlayerRole + "]" + lastPlayer + (isMe ? "(你)" : "") + "已出牌");
          }
          // 显示出牌
          showPokerInfo(lastPokerInfo);
          // 刷新角色
          player.flushRole();
          // 结束游戏
          if (isOver) {
            showGameOver(playerNode.getRole());
            return;
          }
        }

        // 下一个玩家是自己时，显示出牌按钮
        if (nextPlayerNode == currentPlayer) {
          showOutPokerButton(false);
        } else {
          // 下一个玩家不是自己时，下一个玩家显示提示“思考中...”
          playerMap.get(nextPlayerNode.getPlayer()).showTips(isHard ? "Debugging..." : "思考中...");
        }
        // 可以控制机器人
        if (controlRobot) {
          // 上一玩家
          PlayerNode lastPlayerNode = lastPlayer == null ? null : playerMap.get(lastPlayer).getPlayerNode();
          // 控制机器人出牌
          controlRobotOutPoker(nextPlayerNode, lastPlayerNode, lastPokerInfo);
        }
        break;
    }
  }

  /**
   * 显示游戏结束
   *
   * @param role 角色（1.农民 2.地主）
   */
  private void showGameOver(int role) {
    boolean isHard = isHard();
    String roleName = role == 1 ? (isHard ? "Slave" : "农民") : (isHard ? "Master" : "地主");
    state = role == currentPlayer.getRole() ? 5 : 4;
    // 显示提示
    showTips(roleName + (isHard ? " Victory!" : "胜利！"));
    // 显示游戏结束按钮
    if (getRoom() != null) {
      gameOverButton.setVisible(true);
    }
  }

  /**
   * 显示游戏面板
   */
  private void showGamePanel() {
    // 设置面板大小及边界布局
    mainPanel.removeAll();
    mainPanel.setLayout(new BorderLayout());
    mainPanel.setMinimumSize(new Dimension(490, 350));

    // 游戏面板
    JPanel panel = new JPanel();
    // 设置此组件的首选大小
    panel.setPreferredSize(new Dimension(480, 300));
    panel.setLayout(new BorderLayout(10, 10));

    // 左面板
    JPanel leftPanel = new JPanel();
    // 右面板
    JPanel rightPanel = new JPanel();
    // 底部面板
    JPanel bottomPanel = new JPanel();
    // 中心面板
    JPanel centerPanel = new JPanel();

    // 文本面板
    JPanel textPanel = new JPanel();
    // 游戏面板中心提示文本组件
    tipsLabel = new JLabel();
    // 水平中心对齐
    tipsLabel.setHorizontalAlignment(SwingConstants.CENTER);
    // 设置此组件的首选大小
    tipsLabel.setPreferredSize(new Dimension(200, 40));
    tipsLabel.setForeground(playerTipsColor);
    // 将文本面板加入文本面板
    textPanel.add(tipsLabel);
    // 将文本面板加入中心面板
    centerPanel.add(textPanel);

    // 展示出牌显示面板
    showPokerPanel = new JPanel(new BorderLayout());
    // 设置此组件的首选大小
    showPokerPanel.setPreferredSize(new Dimension(200, 70));

    // 出牌面板
    outPokerPanel = new JPanel();
    JBScrollPane pokersScroll = new JBScrollPane(outPokerPanel);
    // 水平滚动（水平滚动条只在需要时显示）
    pokersScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    // 使垂直滚动条从不显示
    pokersScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
    // 加入滚动条
    showPokerPanel.add(pokersScroll);
    // 出牌显示面板加入中心面板
    centerPanel.add(showPokerPanel);

    // 游戏面板,加入左、右、底部、中心面板
    panel.add(leftPanel, BorderLayout.WEST);
    panel.add(rightPanel, BorderLayout.EAST);
    panel.add(bottomPanel, BorderLayout.SOUTH);
    panel.add(centerPanel, BorderLayout.CENTER);

    // 设置当前玩家设置为底部面板
    playerMap.get(currentPlayer.getPlayer()).setPanel(bottomPanel);
    // 将当前玩家前一位所在玩家设置在左边面板
    if (currentPlayer.getPrevPlayer() != null) {
      playerMap.get(currentPlayer.getPrevPlayer().getPlayer()).setPanel(leftPanel);
    }
    // 将当前玩家前一位所在玩家设置在右边面板
    if (currentPlayer.getNextPlayer() != null) {
      playerMap.get(currentPlayer.getNextPlayer().getPlayer()).setPanel(rightPanel);
    }
    // 玩家集合刷新面板
    playerMap.forEach((k, v) -> flushPlayerPanel(v));

    // 主面板
    JPanel mainTopPanel = new JPanel();
    // 标题
    String title = "斗地主！";
    if (isHard()) {
      title = "Synergy Debugging";
    } else if (debugMode == DebugMode.SOFT) {
      title = "";
    }
    // 主面板标题文本组件
    titleLabel = new JLabel(title);
    titleLabel.setFont(new Font("", 1, 14));
    mainTopPanel.add(titleLabel);

    // 主面板底部面板
    JPanel mainBottomPanel = new JPanel();
    if (getRoom() == null) {
      backButton = getBackButton();
      mainBottomPanel.add(backButton);
    }

    // 游戏结束按钮，加入底部主面板，且不可见
    gameOverButton = getGameOverButton();
    gameOverButton.setVisible(false);
    mainBottomPanel.add(gameOverButton);

    // 底部盒子
    Box hBox = Box.createHorizontalBox();
    // 设置窗口模式下拉列表框
    hBox.add(new JLabel("Window: "));
    hBox.add(getWindowModeComboBox());
    hBox.add(Box.createHorizontalStrut(5));
    // debug模式
    hBox.add(new JLabel("Debug Mode: "));
    hBox.add(getDebugModelComboBox());
    mainBottomPanel.add(hBox);

    // 主面板提示在北边，即顶部
    mainPanel.add(mainTopPanel, BorderLayout.NORTH);
    // 游戏面板在中间
    mainPanel.add(panel, BorderLayout.CENTER);
    // 底部主面板在底部
    mainPanel.add(mainBottomPanel, BorderLayout.SOUTH);
    // 占位符面板
    JPanel placeholderPanel = new JPanel();
    placeholderPanel.setMinimumSize(new Dimension(5, 0));
    mainPanel.add(placeholderPanel, BorderLayout.WEST);
    mainPanel.add(placeholderPanel, BorderLayout.EAST);
    mainPanel.updateUI();
    // 刷新debug模式
    flushDebugMode();
  }

  /**
   * 手牌边框
   */
  private Border pokerBorder = BorderFactory.createLineBorder(new Color(0, 0, 0));
  /**
   * 手牌选中边框
   */
  private Border pokerSelectedBorder = BorderFactory.createLineBorder(new Color(255, 104, 104));
  /**
   * 玩家提示颜色
   */
  private Color playerTipsColor = new Color(241, 135, 135);

  /**
   * 刷新玩家面板
   *
   * @param player 游戏玩家
   */
  private void flushPlayerPanel(Player player) {
    // 是否为hard模式
    boolean isHard = isHard();
    // 玩家节点
    PlayerNode playerNode = player.getPlayerNode();
    // 玩家面板
    JPanel panel = player.getPanel();
    // 移除所有组件
    panel.removeAll();

    // 昵称文本组件
    String nickname = isHard ? playerNode.getAlias() : playerNode.getPlayer();
    JLabel nicknameLabel = new JLabel("<html>" + nickname + "</html>");
    // 角色文本组件
    JLabel roleLabel = new JLabel();

    // 当为当前玩家时
    if (playerNode == currentPlayer) {
      // 玩家主面板
      playerMainPanel = new JPanel();
      // 当前玩家所在主面板的顶部面板
      playerTopPanel = new JPanel();
      // 手牌主面板
      pokerMainPanel = new JPanel(new BorderLayout());
      // 底部手牌列表显示面板
      pokerListPanel = new JPanel();

      // 将底部面板、主面板加入玩家面板
      panel.add(playerTopPanel);
      panel.add(playerMainPanel);

      // 按窗口模式刷新面板
      flushWindowMode();
      // 刷新手牌
      flushPokers();

      // 给底部手牌列表显示面板添加水平滚动条
      JBScrollPane pokersScroll = new JBScrollPane(pokerListPanel);
      pokersScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      pokersScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

      // 将底部手牌列表加入手牌主面板
      pokerMainPanel.add(pokersScroll);

      // 将手牌主面板加入玩家主面板
      playerMainPanel.add(pokerMainPanel);

      // 设置此组件的首选大小
      nicknameLabel.setPreferredSize(new Dimension(300, 30));
      nicknameLabel.setText(nickname);
      // 添加昵称文本
      playerMainPanel.add(nicknameLabel);

      // 添加角色文本
      playerMainPanel.add(roleLabel);
    } else {
      // 其它玩家面板
      JPanel otherPlayerPanel = new JPanel();
      // 其它玩家面板
      otherPlayerPanel.setLayout(new BorderLayout());
      // 设置此组件的首选大小
      otherPlayerPanel.setPreferredSize(new Dimension(120, 100));
      // 创建带有“蚀刻”外观的边框
      otherPlayerPanel.setBorder(BorderFactory.createEtchedBorder());
      // 昵称居中
      nicknameLabel.setHorizontalAlignment(SwingConstants.CENTER);
      nicknameLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
      // 加入昵称
      otherPlayerPanel.add(nicknameLabel, BorderLayout.NORTH);
      int alignment;
      Border border;
      // 当前玩家在的下一个玩家左对齐
      if (currentPlayer.getNextPlayer() == playerNode) {
        alignment = SwingConstants.LEFT;
        border = BorderFactory.createEmptyBorder(0, 5, 5, 0);
      } else {
        // 上一个玩家右对齐
        alignment = SwingConstants.RIGHT;
        border = BorderFactory.createEmptyBorder(0, 0, 5, 5);
      }

      // 设置角色
      roleLabel.setBorder(border);
      roleLabel.setHorizontalAlignment(alignment);
      otherPlayerPanel.add(roleLabel, BorderLayout.SOUTH);

      // 玩家提示
      JLabel tipsLabel = new JLabel("");
      tipsLabel.setForeground(playerTipsColor);
      tipsLabel.setHorizontalAlignment(SwingConstants.CENTER);
      player.setTipsLabel(tipsLabel);
      otherPlayerPanel.add(tipsLabel, BorderLayout.CENTER);

      panel.add(otherPlayerPanel);
    }

    // 设置昵称文本
    player.setNicknameLabel(nicknameLabel);
    // 设置角色文本组件
    player.setRoleLabel(roleLabel);
    // 刷新角色文本
    player.flushRole();
    // 更新界面
    panel.updateUI();
  }

  /**
   * 刷新手牌
   */
  private void flushPokers() {
    // 玩家有手牌且底部手牌列表显示面板已初始化
    if (pokers != null && pokerListPanel != null) {
      // 移除所有组件
      pokerListPanel.removeAll();

      // 遍历手牌
      pokers.forEach(poker -> {
        // 扑克牌面板
        JPanel pokerPanel = getPokerPanel(poker);
        // 手牌列表显示面板添加扑克牌面板
        pokerListPanel.add(pokerPanel);
        // 手牌被选中，设置边框颜色
        if (selectedPokers.contains(poker)) {
          pokerPanel.setBorder(pokerSelectedBorder);
        } else {
          // 默认颜色
          pokerPanel.setBorder(pokerBorder);
        }

        // 扑克牌面板鼠标监听
        pokerPanel.addMouseListener(new MouseAdapter() {
          @Override
          public void mouseClicked(MouseEvent e) {

            // 不是出牌状态，跳出
            if (state != 3) {
              return;
            }

            // 是否选中
            boolean selected = selectedPokers.remove(poker);
            // 设置取消选中时的边框
            if (selected) {
              pokerPanel.setBorder(pokerBorder);
            } else {
              // 设置选中时的边框，并添加至已选中
              selectedPokers.add(poker);
              pokerPanel.setBorder(pokerSelectedBorder);
            }

            // 获取选中的扑克版型信息
            pokerInfo = PokerUtil.getPokerInfo(selectedPokers);
            // 如果出牌按钮已初始化
            if (outPokerButton != null) {
              // 存在牌型且出的牌比上家出的大，显示出牌
              outPokerButton.setEnabled(pokerInfo != null && pokerInfo.biggerThanIt(lastPokerInfo));
            }
          }
        });
      });
      // 更新界面
      pokerListPanel.updateUI();
    }
  }

  /**
   * 显示出牌按钮
   *
   * @param started 是否是刚开始时，false,已经开始一段时间，true,刚开始时
   */
  private void showOutPokerButton(boolean started) {

    boolean isHard = isHard();
    // 当前是否为上家
    boolean lastIsMe = currentPlayer.getPlayer().equals(lastPlayer);
    // 当前为上家时,上家出牌信息置空
    if (lastIsMe) {
      lastPokerInfo = null;
    }

    // 当前玩家所在主面板的顶部面板移除所有组件
    playerTopPanel.removeAll();

    // 重置按钮
    resetButton = new JButton(isHard ? "Reset" : "重置");
    resetButton.addActionListener(e -> {
      // 未选中牌，跳过
      if (selectedPokers.isEmpty()) {
        return;
      }

      // 清空选中的扑克
      selectedPokers.clear();
      // 当前出牌信息为空
      pokerInfo = null;
      // 刷新手牌
      flushPokers();
      // 出牌按钮禁用
      outPokerButton.setEnabled(false);
    });

    // 添加重置按钮
    playerTopPanel.add(resetButton);

    // 已经开始一段时间，即牌中时，且当前不是上家
    if (!started && !lastIsMe) {
      // 不出牌按钮
      notOutPokerButton = new JButton(isHard ? "Run!" : "过！");
      notOutPokerButton.addActionListener(e -> {
        // 非出牌阶段，跳过
        if (state != 3) {
          return;
        }

        // 发送出牌消息
        sendMsg(LandlordsGameDTO.MsgType.OUT_POKER, null);
      });

      // 添加不出牌按钮
      playerTopPanel.add(notOutPokerButton);
    }

    // 出牌按钮
    outPokerButton = new JButton(isHard ? "Debug" : "出牌");
    // 当前出牌信息比上一次牌面大,放开出牌按钮
    outPokerButton.setEnabled(pokerInfo != null && pokerInfo.biggerThanIt(lastPokerInfo));
    outPokerButton.addActionListener(e -> {
      // 非出牌阶段跳过
      if (state != 3) {
        return;
      }

      // 已出牌
      if (pokerInfo != null) {
        // 出牌按钮禁用
        outPokerButton.setEnabled(false);
        // 从手牌中移除选中的牌
        pokers.removeAll(selectedPokers);
        // 清空已选中的牌列表
        selectedPokers.clear();
        // 刷新当前玩家面板
        flushPlayerPanel(playerMap.get(currentPlayer.getPlayer()));

        // 发送出牌消息
        PokerInfo copy = BeanUtil.copyProperties(pokerInfo, PokerInfo.class);
        sendMsg(LandlordsGameDTO.MsgType.OUT_POKER, copy);

        // 设置玩家工具类中的手牌列表
        helpPlayerAction.setPokers(new ArrayList<>(pokers));
        pokerInfo = null;
      }
    });

    // 添加出牌按钮
    playerTopPanel.add(outPokerButton);

    // 提示按钮
    helpButton = new JButton(isHard ? "Help!" : "提示");
    // 提示按钮监听
    helpButton.addActionListener(e -> {
      // 清空选中
      selectedPokers.clear();

      PlayerNode playerNode = null;
      // 上家出牌的玩家节点
      if (lastPlayer != null) {
        playerNode = playerMap.get(lastPlayer).getPlayerNode();
      }
      // 提示出牌信息
      pokerInfo = helpPlayerAction.getOutPoker(playerNode, lastPokerInfo);

      // 提示的出牌信息存在时
      boolean isOut = pokerInfo != null;
      if (isOut) {
        // 将出牌信息添加进已选中的牌列表中
        for (Poker poker : pokerInfo.getPokers()) {
          selectedPokers.add(poker);
        }
      }

      // 刷新手牌
      flushPokers();
      // 有提示，启用出牌按钮
      outPokerButton.setEnabled(isOut);
    });

    // 添加提示按钮
    playerTopPanel.add(helpButton);
    // 更新界面
    playerTopPanel.updateUI();
  }

  /**
   * 展示叫分按钮
   *
   * @param score 分值
   */
  private void showCallScoreButton(int score) {
    boolean isHard = isHard();
    playerTopPanel.removeAll();
    // 不叫分按钮
    notCallScoreButton = new JButton(isHard ? "Abandon!" : "不叫");
    notCallScoreButton.addActionListener(e -> {
      // 当前不是叫分状态，就跳过
      if (state != 2) {
        return;
      }
      // 发送叫分信息
      sendMsg(LandlordsGameDTO.MsgType.CALL_SCORE, 0);
    });
    playerTopPanel.add(notCallScoreButton);

    // 添加叫分按钮
    for (int i = score + 1; i < 4; i++) {
      int callScore = i;
      callScoreButton = new JButton(callScore + (isHard ? " Point" : "分"));
      callScoreButton.addActionListener(e -> {
        // 当前不是叫分状态，就跳过
        if (state != 2) {
          return;
        }

        // 发送叫分信息
        sendMsg(LandlordsGameDTO.MsgType.CALL_SCORE, callScore);
      });
      playerTopPanel.add(callScoreButton);
    }

    // 更新界面
    playerTopPanel.updateUI();
  }

  /**
   * 展示玩家提示
   *
   * @param tips 提示
   */
  private void showPlayerTips(String tips) {
    playerTopPanel.removeAll();
    JLabel label = new JLabel(tips);
    label.setForeground(playerTipsColor);
    // 出牌后，按钮消失，但显示出牌的提示信息
    playerTopPanel.add(label);
    playerTopPanel.updateUI();
  }

  /**
   * 获取扑克牌面板
   *
   * @param poker 扑克牌对象
   * @return 获取扑克牌面板
   */
  private JPanel getPokerPanel(Poker poker) {
    // 是否为hard模式
    boolean isHard = isHard();
    // 是否不是默认模式
    boolean isNotDefault = debugMode != null && debugMode != DebugMode.DEFAULT;

    // 扑克牌面板
    JPanel pokerPanel = new JPanel();
    // 扑克牌边框
    pokerPanel.setBorder(pokerBorder);

    // 扑克牌背景色
    pokerPanel.setBackground(isNotDefault ? null : new Color(217, 214, 214));
    // 设置此组件的首选大小
    pokerPanel.setPreferredSize(new Dimension(30, 55));
    pokerPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

    // 牌值
    int value = poker.getValue();
    // 花色
    Poker.Suits suits = poker.getSuits();
    // 扑克牌颜色
    Color pokerColor;
    // 黑桃、梅花，默认模式是黑色，否则是红色
    if (suits == Poker.Suits.SPADE || suits == Poker.Suits.CLUB || value == 16) {
      pokerColor = isNotDefault ? null : new Color(0, 0, 0);
    } else {
      pokerColor = isNotDefault ? null : new Color(252, 78, 78);
    }

    // 显示值
    String displayValue = getPokerDisplayValue(value);
    // 手牌值
    JLabel pokerValue = new JLabel(" " + (isHard ? getPokerDisplayValueForHard(value) : displayValue));
    pokerValue.setForeground(pokerColor);
    // 设置此组件的首选大小
    pokerValue.setPreferredSize(new Dimension(30, 12));

    // 花色
    JLabel pokerSuits = new JLabel(" " + getPokerDisplaySuits(suits));
    pokerSuits.setForeground(pokerColor);
    // 设置此组件的首选大小
    pokerSuits.setPreferredSize(new Dimension(30, 10));

    // 设置提示文本
    pokerPanel.setToolTipText(displayValue);
    // 添加牌值文本
    pokerPanel.add(pokerValue);
    // 添加花色文本
    pokerPanel.add(pokerSuits);

    return pokerPanel;
  }

  /**
   * 展示提示
   *
   * @param tips 提示文本
   */
  private void showTips(String tips) {
    tipsLabel.setText("<html>" + tips + "</html>");
    tipsLabel.updateUI();
  }

  /**
   * 显示扑克组相关信息
   *
   * @param pokerInfo 扑克组相关信息
   */
  private void showPokerInfo(PokerInfo pokerInfo) {
    showPokers(pokerInfo.getPokers());
  }

  /**
   * 展示出牌列表
   *
   * @param pokerList 扑克列表（出牌列表）
   */
  private void showPokers(List<Poker> pokerList) {
    outPokerPanel.removeAll();
    // 出牌列表不为空，添加相关扑克面板
    if (pokerList != null) {
      pokerList.forEach(poker -> outPokerPanel.add(getPokerPanel(poker)));
    }
    // 更新界面
    outPokerPanel.updateUI();
  }

  /**
   * 获取扑克显示的牌型
   *
   * @param pokerInfo 扑克相关信息
   * @return 扑克的牌型
   */
  private String getPokerDisplayModel(PokerInfo pokerInfo) {
    boolean isHard = isHard();
    if (pokerInfo != null) {
      // 最小值
      String minValue = getPokerDisplayValue(pokerInfo.getPokers().get(0).getValue());
      // 最大值
      String maxValue = getPokerDisplayValue(pokerInfo.getValue());
      switch (pokerInfo.getPokerModel()) {
        case ROCKET:
          return isHard ? "Rocket" : "王炸";
        case BOMB:
          return isHard ? "Bomb" : "炸弹";
        case SINGLE:
          return isHard ? "Single" : "单张" + minValue;
        case PAIR:
          return isHard ? "Pair" : "对" + minValue;
        case THREE:
          return isHard ? "Three" : "三张" + minValue;
        case THREE_ONE_SINGLE:
          return isHard ? "Three Belt One" : "三带一";
        case THREE_ONE_PAIR:
          return isHard ? "Three Belt One Pair" : "三带一对";
        case SHUN_ZI_SINGLE:
          return isHard ? "Single Link" : "单顺子(" + minValue + "~" + maxValue + ")";
        case SHUN_ZI_PAIR:
          return isHard ? "Pair Link" : "双顺子(" + minValue + "~" + maxValue + ")";
        case PLAIN_UNMANNED:
          return isHard ? "Plain Unmanned" : "无人飞机";
        case PLAIN_MANNED:
          return isHard ? "Plain Manned" : "载人飞机";
        case FOUR_TWO_SINGLE:
          return isHard ? "Four Belt Two Single" : "四带二";
        case FOUR_TWO_PAIR:
          return isHard ? "Four Belt Two Pair" : "四带俩对";
      }
    }

    return "";
  }

  /**
   * 获取手牌显示的值
   *
   * @param value 牌值
   * @return 手牌实际显示的值
   */
  private String getPokerDisplayValue(int value) {
    switch (value) {
      case 11:
        return "J";
      case 12:
        return "Q";
      case 13:
        return "K";
      case 14:
        return "A";
      case 15:
        return "2";
      case 16:
        return "王";
      case 17:
        return "皇";
      default:
        return value + "";
    }
  }

  /**
   * 获取手牌在hard模式下显示的值
   *
   * @param value 牌值
   * @return 手牌在hard模式下显示的值
   */
  private String getPokerDisplayValueForHard(int value) {
    switch (value) {
      case 10:
        return "0x0";
      case 11:
        return "0xB";
      case 12:
        return "0xC";
      case 13:
        return "0xD";
      case 14:
        return "0xA";
      case 15:
        return "0x2";
      case 16:
        return "0xE";
      case 17:
        return "0xF";
      default:
        return "0x" + value;
    }
  }

  /**
   * 获取手牌显示的花色
   *
   * @param suits 花色
   * @return 手牌显示的花色
   */
  private String getPokerDisplaySuits(Poker.Suits suits) {
    if (suits == null || debugMode == DebugMode.SOFT) {
      return "";
    }

    if (isHard()) {
      return "int";
    }

    switch (suits) {
      case SPADE:
        return "♠";
      case HEART:
        return "♥";
      case DIAMOND:
        return "♦";
      case CLUB:
        return "♣";
      default:
        return "";
    }
  }

  /**
   * 获取开始游戏按钮
   *
   * @return 开始游戏按钮
   */
  private JButton getStartGameButton() {
    JButton button = new JButton("开始游戏");
    button.addActionListener(e -> {
      // 默认是禁用的
      button.setEnabled(false);
      invoke(() -> {
        // 设置当前为房主
        setHomeowner(true);
        // 开始游戏
        start();
        // 按钮启用
        button.setEnabled(true);
      }, 100);
    });
    return button;
  }

  /**
   * 获取返回游戏按钮
   *
   * @return 返回游戏按钮
   */
  private JButton getBackButton() {
    JButton button = new JButton(isHard() ? "Back Debug" : "返回游戏");
    // 添加初始化行为
    button.addActionListener(e -> init());
    return button;
  }

  /**
   * 构建游戏玩家节点，将所有玩家节点形成一个闭环
   */
  private void buildPlayerNode() {
    // 开始的玩家节点
    PlayerNode startNode = null;
    // 其它玩家节点
    PlayerNode playerNode = null;
    // 玩家缓存
    playerMap = new HashMap<>();
    // 游戏房间内玩家列表
    List<String> roomUserList = userList;
    // 房间玩家总数
    int usersTotal = roomUserList.size();

    for (int i = 0; i < usersTotal; i++) {
      PlayerNode node = new PlayerNode();
      // 设置玩家名称
      node.setPlayer(roomUserList.get(i));
      // 设置手牌数量
      node.setPokerTotal(17);
      // 设置玩家别名
      node.setAlias("Machine 0" + (i + 1));
      // 加入缓存
      playerMap.put(node.getPlayer(), new Player(node, null));

      // 当昵称与玩家节点名称相等时
      if (GameAction.getNickname().equals(node.getPlayer())) {
        // 当前玩家
        currentPlayer = node;
        // 初始化玩家行为帮助工具
        helpPlayerAction = new AIPlayerAction(currentPlayer);
      }

      // 如果ai玩家行为Map存在指定的key设置相关的游戏行为对象
      if (aiPlayerActionMap.containsKey(node.getPlayer())) {
        aiPlayerActionMap.put(node.getPlayer(), new AIPlayerAction(node));
      }

      // 如果游戏节点为空，设置玩家节点
      if (playerNode == null) {
        playerNode = node;
        startNode = node;
        continue;
      }

      // 当为最后一个玩家时,设置节点形成闭环
      if (i == usersTotal - 1) {
        // 设置当前节点的下一个节点为起始节点
        node.setNextPlayer(startNode);
        // 设置起始节点的上一个节点为当前节点
        startNode.setPrevPlayer(node);
      }

      // 设置玩家的下一个节点为当前节点
      playerNode.setNextPlayer(node);
      // 设置当前节点的上一个节点为玩家节点
      node.setPrevPlayer(playerNode);
      // 设置玩家节点
      playerNode = node;
    }
  }

  /**
   * 接受并且都准备好了
   *
   * @return true, 三个玩家都接受了，false,仍有玩家没有接受
   */
  private boolean receivedAndOk() {
    if (receiveCounter.incrementAndGet() == 3) {
      receiveCounter.set(0);
      return true;
    }
    return false;
  }

  @Override
  public void playerLeft(User player) {
    // 玩家离开
    super.playerLeft(player);
    // 游戏已开始且没有分出胜负时
    if (state > 0 && state < 4) {

      // 设置当前为负
      state = 4;
      String msg = "游戏结束！" + player.getUsername() + "逃跑了~";
      String tips = "溜了~";

      // hard模式下的消息和提示
      if (isHard()) {
        msg = "Debugging is over. The " + playerMap.get(player.getUsername()).getPlayerNode().getAlias() + " has been offline";
        tips = "Offline~";
      }
      // 面板中心显示提示
      showTips(msg);
      // 玩家面板展示提示
      Player leftPlayer = playerMap.get(player.getUsername());
      if (leftPlayer != null) {
        leftPlayer.showTips(tips);
      }
      // 结束游戏按钮可见
      gameOverButton.setVisible(true);
    }
  }

  /**
   * debug模式
   */
  @Getter
  @AllArgsConstructor
  public enum DebugMode {
    /**
     * 默认模式
     */
    DEFAULT("Default"),
    /**
     * 软模式
     */
    SOFT("Soft"),
    /**
     * 硬模式
     */
    HARD("Hard");

    /**
     * debug模式名称
     */
    private String name;

    /**
     * 通过名称值配置
     *
     * @param name 待匹配的名称
     * @return debug模式选项
     */
    public static DebugMode getMode(String name) {
      return Arrays.stream(values()).filter(mode -> mode.getName().equals(name)).findFirst().orElse(null);
    }
  }

  /**
   * 窗口模式
   */
  @Getter
  @AllArgsConstructor
  public enum WindowMode {
    /**
     * 默认
     */
    DEFAULT("Default"),
    /**
     * 自适应
     */
    ADAPTIVE("Adaptive");
    /**
     * 窗口模式名称
     */
    private String name;

    /**
     * 通过名称值配置
     *
     * @param name 待匹配的名称
     * @return 窗口模式
     */
    public static WindowMode getMode(String name) {
      return Arrays.stream(values()).filter(mode -> mode.getName().equals(name)).findFirst().orElse(null);
    }
  }

  /**
   * 获取debug模式下拉列表框
   *
   * @return debug模式下拉列表框
   */
  private ComboBox getDebugModelComboBox() {
    // 下拉列表框
    ComboBox comboBox = new ComboBox();
    // 为下拉列表框添加选项
    for (DebugMode mode : DebugMode.values()) {
      comboBox.addItem(mode.getName());
      // 如果模式相等，则选中
      if (debugMode == mode) {
        comboBox.setSelectedItem(mode.getName());
      }
    }

    // 下拉列表选项监听
    comboBox.addItemListener(e -> {
      // 设置debug模式
      debugMode = DebugMode.getMode(comboBox.getSelectedItem().toString());
      // 刷新debug模式
      flushDebugMode();
    });
    return comboBox;
  }

  /**
   * 刷新debug模式
   */
  private void flushDebugMode() {
    // 标题文本
    String title = "斗地主！";
    // 返回按钮文本
    String backButtonText = "返回游戏";
    // 游戏结束按钮文本
    String gameOverButtonText = "游戏结束";
    // 不出牌按钮文本
    String notOutPokerButtonText = "过！";
    // 出牌按钮文本
    String outPokerButtonText = "出牌";
    // 重置按钮文本
    String resetButtonText = "重置";

    // 设置hard模式下的文本
    if (isHard()) {
      title = "Synergy Debugging";
      backButtonText = "Back";
      gameOverButtonText = "Debug Over";
      notOutPokerButtonText = "Run!";
      outPokerButtonText = "Debug";
      resetButtonText = "Reset";
    }

    // 设置soft模式下的文本
    if (debugMode == DebugMode.SOFT) {
      title = "";
      backButtonText = "返回";
      gameOverButtonText = "结束";
    }

    // 设置返回按钮文本
    if (backButton != null) {
      backButton.setText(backButtonText);
      backButton.updateUI();
    }

    // 设置不出牌按钮文本
    if (notOutPokerButton != null) {
      notOutPokerButton.setText(notOutPokerButtonText);
      notOutPokerButton.updateUI();
    }

    // 设置出牌按钮文本
    if (outPokerButton != null) {
      outPokerButton.setText(outPokerButtonText);
      outPokerButton.updateUI();
    }

    // 设置重置按钮文本
    if (resetButton != null) {
      resetButton.setText(resetButtonText);
      resetButton.updateUI();
    }

    // 设置标题
    titleLabel.setText(title);
    titleLabel.updateUI();

    // 设置游戏结束按钮文本
    gameOverButton.setText(gameOverButtonText);
    gameOverButton.updateUI();

    // 刷新玩家角色和昵称文本
    playerMap.forEach((k, v) -> {
      v.flushRole();
      v.flushNickname();
    });

    // 刷新手牌列表
    flushPokers();

    // 刷新上家出牌列表
    if (lastPokerInfo != null) {
      showPokerInfo(lastPokerInfo);
    }
  }


  /**
   * 获取窗口模式下拉列表框
   *
   * @return 窗口模式下拉列表框
   */
  private ComboBox getWindowModeComboBox() {
    // 下拉列表框
    ComboBox comboBox = new ComboBox();
    // 为下拉列表框添加选项
    for (WindowMode mode : WindowMode.values()) {
      comboBox.addItem(mode.getName());
      // 如果模式相等，则选中
      if (windowMode == mode) {
        comboBox.setSelectedItem(mode.getName());
      }
    }

    // 下拉列表选项监听
    comboBox.addItemListener(e -> {
      // 设置窗口模式
      windowMode = WindowMode.getMode(comboBox.getSelectedItem().toString());
      // 刷新窗口模式
      flushWindowMode();
    });
    return comboBox;
  }

  /**
   * 刷新窗口模式
   */
  private void flushWindowMode() {
    int width = 490;
    int topWidth = 200;

    // 窗口自适应
    if (windowMode == WindowMode.ADAPTIVE) {
      // 手牌数量
      int pokerSize = CollectionUtil.size(pokers);
      // 当为地主时，20张牌，农民则为17张牌，
      pokerSize = Math.max(currentPlayer.getRole() == 2 ? 20 : 17, pokerSize);
      // 计算宽度
      width = pokerSize * 36;
      // 当为地主时，顶部中心面板宽度为300
      if (pokerSize == 20) {
        topWidth = 300;
      }
    }

    // 设置组件的首选大小
    // 主面板
    mainPanel.setMinimumSize(new Dimension(width, 350));
    // 面板中心提示文本组件
    tipsLabel.setPreferredSize(new Dimension(topWidth, 40));
    // 出牌显示面板
    showPokerPanel.setPreferredSize(new Dimension(topWidth, 70));
    // 玩家主面板
    playerMainPanel.setPreferredSize(new Dimension(width, 150));
    // 当前玩家所在主面板的顶部面板
    playerTopPanel.setPreferredSize(new Dimension(width, 40));
    // 手牌主面板
    pokerMainPanel.setPreferredSize(new Dimension(width - 10, 70));
    // 刷新主面板
    mainPanel.updateUI();
  }


  /**
   * 是否hard模式
   *
   * @return true, hard模式，否则，false
   */
  public static boolean isHard() {
    return debugMode == DebugMode.HARD;
  }

}
